'''
#of common neighbors, considering incoming, outgoing, to dst, to src, both
# of common neighbors + # of 3 away
# of edges total, dst, src, incoming, outgoing, src/dst, incoming/outgoing
# of edges of nearest neighbors
shortest path from dst to src
# of reciprocal edges vs unreciprocal for src dst and ratio
'''

import Parser
from datetime import datetime
from random import random

#global localGraph
localGraph = None

def distance(node1, node2):
  from1 = set([node1])
  from2 = set([node2])
  if from1.intersection(from2):
    return [0]
  for node in list(from1):
    if not (node in localGraph.Incoming and node in localGraph.Outgoing):
      continue
    from1.update(set(localGraph.Incoming[node]).intersection(set(localGraph.Outgoing[node])))
  if from1.intersection(from2):
    return [1]
  for node in list(from2):
    if not (node in localGraph.Incoming and node in localGraph.Outgoing):
      continue
    from2.update(set(localGraph.Incoming[node]).intersection(set(localGraph.Outgoing[node])))
  if from1.intersection(from2):
    return [2]
  for node in list(from1):
    if not (node in localGraph.Incoming and node in localGraph.Outgoing):
      continue
    from1.update(set(localGraph.Incoming[node]).intersection(set(localGraph.Outgoing[node])))
  if from1.intersection(from2):
    return [3]
  for node in list(from2):
    if not (node in localGraph.Incoming and node in localGraph.Outgoing):
      continue
    from2.update(set(localGraph.Incoming[node]).intersection(set(localGraph.Outgoing[node])))
  if from1.intersection(from2):
    return [4]
  return [5]
  #return [localGraph.GetDistance(node1, node2)]

def commonNeighbors(node1, node2):
  a = set([node1])
  b = set([node2])
  a.update(localGraph.GetNeighbors(node1))
  try:
    b.update(localGraph.GetNeighbors(node2))
  except:
    print 'node1:', node1
    print 'node2:', node2
    print localGraph.Incoming[node1]
    print localGraph.Outgoing[node1]
    exit()
  common1 = len(a.intersection(b))
  for node in localGraph.GetNeighbors(node1):
    a.update(localGraph.GetNeighbors(node))
  for node in localGraph.GetNeighbors(node2):
    b.update(localGraph.GetNeighbors(node))
  common2 = len(a.intersection(b))
  return [common1, common2]

def totalEdges(a, b):
  # Edges coming into both nodes
  intoA = len(localGraph.Incoming[a])
  intoB = len(localGraph.Incoming[b])
  
  # Edges going out from both nodes
  outfromA = len(localGraph.Outgoing[a])
  outfromB = len(localGraph.Outgoing[b])
  
  # Reciprocated relationships
  recipA = len(localGraph.GetNeighbors(a, 3))
  recipB = len(localGraph.GetNeighbors(b, 3))
  
  return [intoA, intoB, (intoA+1.0)/(intoB+1.0), outfromA, outfromB, (outfromA+1.0)/(outfromB+1.0), recipA, recipB, (recipA+1.0)/(recipB+1.0), (intoA+1.0)/(outfromA+1.0), (intoB+1.0)/(outfromB+1.0), (intoA+1.0)/(recipA+1.0), (intoB+1.0)/(recipB+1.0), (outfromA+1.0)/(recipA+1.0), (outfromB+1.0)/(recipB+1.0)]

#4. A ratio of the sum of incoming connections to the number of reciprocated neighbors they share.
#5. A ratio of the sum of outgoing connections to the number of reciprocated neighbors they share.
def ratioIOrec(node1, node2, recnum=float('inf')):
  recip1 = localGraph.GetNeighbors(node1, 3)
  recip2 = localGraph.GetNeighbors(node2, 3)
  recnum = len(recip1.intersection(recip2)) + 1
  return [(len(localGraph.Incoming[node1]) + len(localGraph.Incoming[node2])) / recnum , (len(localGraph.Outgoing[node1]) + len(localGraph.Outgoing[node2])) / recnum]

#6. Jaccard's Coefficient, which is the intersection of neighbors over union of neighbors reciprocated.
def GetCoefficient(node1, node2, recnum=0.0):
    #if localGraph.Incoming.get(node1) == None or localGraph.Incoming.get(node2) == None or localGraph.Outgoing.get(node1) == None or localGraph.Outgoing.get(node2) == None:
    #    return 0
    incoming1 = set(localGraph.Incoming[node1])
    incoming2 = set(localGraph.Incoming[node2])
    outgoing1 = set(localGraph.Outgoing[node1])
    outgoing2 = set(localGraph.Outgoing[node2])
    recnum = len(incoming1.union(outgoing1).intersection(incoming2.union(outgoing2)))
    retValue = set()
    retValue.update(incoming1.intersection(outgoing1).union(incoming2.intersection(outgoing2)))
    #if len(retValue) == 0:
    #    return 0
    return [float(recnum) / float(len(retValue)+1)]

#7. A ratio of the sum of incoming connections to the sum of outgoing connections.
#8. A ratio of the sum of outgoing connections to the sum of incoming connections
def ratioIOsumOIsum(node1, node2):
  retval = (len(localGraph.Incoming[node1]) + len(localGraph.Incoming[node2]))/(len(localGraph.Outgoing[node1]) + len(localGraph.Outgoing[node2]) + 1.0)
  return [retval, 1/(retval+1.0)]

#9. A maximum of the number of incoming connections of both users.
#12. A minimum of the number of incoming connections of both users.
def maxminIn(node1, node2):
  return [max(len(localGraph.Incoming[node1]), len(localGraph.Incoming[node2])), min(len(localGraph.Incoming[node1]), len(localGraph.Incoming[node2]))]

#10. A maximum of the number of outgoing connections of both users.
#13. A minimum of the number of outgoing connections of both users.
def maxminOut(node1, node2):
  return [max(len(localGraph.Outgoing[node1]), len(localGraph.Outgoing[node2])), min(len(localGraph.Outgoing[node1]), len(localGraph.Outgoing[node2]))]


#11. A maximum of the number of reciprocated edges of both users.
#14. A minimum of the number of reciprocated edges of both users.
def maxminEdges(node1, node2):
  incoming1 = set(localGraph.Incoming[node1])
  incoming2 = set(localGraph.Incoming[node2])
  outgoing1 = set(localGraph.Outgoing[node1])
  outgoing2 = set(localGraph.Outgoing[node2])
  return [max(len(incoming1.intersection(outgoing1)), len(incoming2.intersection(outgoing2))), min(len(incoming1.intersection(outgoing1)), len(incoming2.intersection(outgoing2)))]

# 36 features
def featureFunctions():
  #       1         2                 15
  return [distance, commonNeighbors, totalEdges, ratioIOrec, GetCoefficient, ratioIOsumOIsum, maxminIn, maxminOut]

def extractFeatures(src, dst):
  features = []
  for f in featureFunctions():
    features += f(src, dst)
  return features

def writeFeatures(featureFile, features):
  for feature in features:
    featureFile.write(' '+str(feature))
  featureFile.write('\n')

def file_len(fname):
    with open(fname) as f:
        for i, l in enumerate(f):
            pass
    return i + 1

if __name__ == '__main__':
  skipProb = 0.91                                         # Change this to the fraction of edges to skip.
  print datetime.now()
  global localGraph
  #Parser.ParserSelectFile('..\data\graph-2011-08-03.txt')
  Parser.ParserSelectFile('..\data\graph-2011-09-02.txt') # Change this to the first snapshot.
  Parser.initMetaFile()
  #edgeName = 'first_999.txt'
  edgeName = 'second_9999.txt'                            # Change this to the input file.
  filelen = file_len(edgeName)
  edgeFile = open(edgeName, 'r')
  #featureFile = open('first_999_36_features.txt', 'w')
  #edgeFile = open('second_9999_36.txt', 'r')
  featureFile = open('second_single-bias.txt', 'w')       # Change this to the output file.
  #src = None
  count = 0
  for line in edgeFile:
    if count%25==0:
      print count,'/',filelen
      featureFile.flush()
    count += 1
    if random() < skipProb:
      continue
    line = line.split()
    #if src != line[0]:
    #print 'creating local graph'
    localGraph = Parser.CreateLocalGraph(line[0], line[1], 1)#, maxStartingEdges=500)
    #src = line[0]
    if not localGraph:
      print 'Too many nodes; skipping'
      #localGraph = Parser.CreateLocalGraph(line[0], line[1], 0)
      #print 'test', len(localGraph.Incoming[line[0]]) + len(localGraph.Outgoing[line[0]]) + len(localGraph.Incoming[line[1]]) + len(localGraph.Outgoing[line[1]])
      continue
    #print len(localGraph.Nodes), 'nodes'
    #print 'test', len(localGraph.Incoming[line[0]]) + len(localGraph.Outgoing[line[0]]) + len(localGraph.Incoming[line[1]]) + len(localGraph.Outgoing[line[1]])
    #if len(localGraph.Nodes) > 1000:
    #  errFile = open('uhoh.txt', 'a')
    #  errFile.write(str(localGraph.Nodes))
    #  errFile.write('\n')
    #  errFile.close()
    #print 'extracting features'
    features = extractFeatures(int(line[0]), int(line[1]))
    featureFile.write(line[2]+' 1') # Bias!
    writeFeatures(featureFile, features)
    featureFile.flush()
  edgeFile.close()
  featureFile.close()
  print datetime.now()